/****************************************************************************
 * vendor/semidrive/chips/common/include/mpu.h
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/
#ifndef __CHIPS_E3650_COMMON_MPU_H
#define __CHIPS_E3650_COMMON_MPU_H

#include <stdint.h>
#include <nuttx/irq.h>
#include "nuttx/compiler.h"
#include "sctlr.h"
#include "mpu_reg.h"
#include "barriers.h"

// [MPU Region]
//   A region is a contiguous range of address starting at a base address,
//   extending up to and including a limit address.
//     PRBAR.BASE:0b000000 <= address <= PRLAR.LIMIT:0b111111
//
// Each memory region is defined by:
//   1. base address  (PRBAR.BASE)
//   2. limit address (PRLAR.LIMIT)
//   3. access permissions
//   4. memory attributes
//
// Note:
//   1. The base address is aligned on a 64-byte boundary and
//   limit address is aligned to the byte below a 64-byte boundary.
//   2. The minimum size for a region is 64 bytes.
//   3. PRBAR and PRLAR also hold:
//       a. access permissions     (PRBLAR.AP)
//       b. shareability           (PRBAR.SH)
//       c. execute-never bit      (PRBAR.XN)
//       d. memory attribute index (PRLAR.AttrIndx)
//       e. region enable bit      (PRLAR.EN)
//   4. Memory attributes are determined by indexing the Memory Attribute
//   Indirection Registers(MAIRx) with PRLAR.AttrIndx.
//   5. A region is enabled or disabled by setting or clearing
//   the region enable bit(PRLAR.EN)
//

// [MPU Background region]
//   The EL1-controlled MPU background region is used as a default
//   memory map when the MPU is disabled(SCTLR.M=0).
//   When the MPU is enabled, the background region can be enabled by
//   setting SCTLR.BR. In this case, accesses from the EL1 translation
//   regime that do not hit any programmable regions use the background
//   region. Accesses from the EL0 translation regime are faulted when
//   the MPU is enabled.
//

// [MPU Registers]
//   Name    CRn    Op1    CRm    Op2    Reset    Description
//   -------------------------------------------------------------------------------------
//   SCTLR   c1     0      c0     0               System Control Register
//   PRSELR  c6     0      c2     1      UNK      Protection Region Selection Register
//   PRBAR   c6     0      c3     0      UNK      Protection Region Base Address Register
//   PRLAR   c6     0      c3     1      UNK      Protection Region Limit Address Register
//   MPUIR   c0     0      c0     4               MPU Type Register
//   MAIR0   c10    0      c2     0               Memory Attribute Index Register 0
//   MAIR1   c10    0      c2     1               Memory Attribute Index Register 1
//
#define ARM_MPU_REGION_ALIGN_SIZE               (64U)

#define MPU_IS_ALIGN(x)   ((x & (ARM_MPU_REGION_ALIGN_SIZE - 1)) == 0)
#define MPU_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

// MPU Cache Attributes
#define ARM_MPU_CACHE_T                         (0)
#define ARM_MPU_CACHE_NT                        (1)
#define ARM_MPU_CACHE_WT                        (0)
#define ARM_MPU_CACHE_WB                        (1)
#define ARM_MPU_CACHE_nRA                       (0)
#define ARM_MPU_CACHE_RA                        (1)
#define ARM_MPU_CACHE_nWA                       (0)
#define ARM_MPU_CACHE_WA                        (1)

// MPU Memory Attributes, Normal Memory
#define ARM_MPU_MEMATTR_MEMORY(NT, WB, RA, WA) \
  ((((NT) & 1U) << 3U) | (((WB) & 1U) << 2U) | (((RA) & 1U) << 1U) | ((WA) & 1U))

// Normal Memory, Non-Cacheable
#define ARM_MPU_MEMATTR_NORMAL_NO_CACHEABLE        (0b0100)

// Device memory
#define ARM_MPU_MEMATTR_DEVICE                     (0b0000)

// Normal Memory, Outer Non-Cacheable.
#define ARM_MPU_MEMATTR_NORMAL_OUTER_NON_CACHEABLE (0b0100)

// Normal memory outer-cacheable
// WT = Write Through, WB = Write Back, TR = Transient, RA = Read-Allocate, WA = Write Allocate
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WT_TR_RA      (0b0010)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WT_TR_WA      (0b0001)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WT_TR_RA_WA   (0b0011)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WT_RA         (0b1010)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WT_WA         (0b1001)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WT_RA_WA      (0b1011)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WB_TR_RA      (0b0101)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WB_TR_WA      (0b0110)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WB_TR_RA_WA   (0b0111)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WB_RA         (0b1101)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WB_WA         (0b1110)
#define ARM_MPU_MEMATTR_NORMAL_OUTER_WB_RA_WA      (0b1111)

// Device memory type non Gathering, Re-ordering, Early Write Acknowledgement
#define ARM_MPU_MEMATTR_DEVICE_nGnRnE              (0b0000U)
#define ARM_MPU_MEMATTR_DEVICE_nGnRE               (0b0100U)
#define ARM_MPU_MEMATTR_DEVICE_nGRE                (0b1000U)
#define ARM_MPU_MEMATTR_DEVICE_GRE                 (0b1100U)

// Normal memory inner-cacheable attributes
// WT = Write Through, WB = Write Back, TR = Transient, RA = Read-Allocate, WA = Write Allocate
#define ARM_MPU_MEMATTR_NORMAL_INNER_NON_CACHEABLE (0b0100)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WT_TR_RA      (0b0010)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WT_TR_WA      (0b0001)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WT_TR_RA_WA   (0b0011)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WT_RA         (0b1010)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WT_WA         (0b1001)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WT_RA_WA      (0b1011)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WB_TR_RA      (0b0101)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WB_TR_WA      (0b0110)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WB_TR_RA_WA   (0b0111)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WB_RA         (0b1101)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WB_WA         (0b1110)
#define ARM_MPU_MEMATTR_NORMAL_INNER_WB_RA_WA      (0b1111)

// Memory Attributes
//   O Outer memory attributes    Attr[7:4]
//   I O == MP_MEMATTR_DEVICE: Device memory attributes, else: Inner memory attributes
#define ARM_MPU_MEMATTR(O, I) \
    (((O & 0xFU) << 4U) | (((O & 0xFU) != 0U) ? (I & 0xFU) : ((I & 0x3U) << 2U)))

// MAIR Register value define
//   attr3 Bit[31: 24]
//   attr2 Bit[23: 16]
//   attr1 Bit[15: 8]
//   attr0 Bit[7 : 0]
#define ARM_MPU_MAIR_REG(attr0, attr1, attr2, attr3) \
    ((((attr3) & 0xFFU) << 24U) | (((attr2) & 0xFFU) << 16U) | \
     (((attr1) & 0xFFU) << 8U ) |  ((attr0) & 0xFFU))

// Shareability encoding for Normal memory (PRBAR [4:3] SH )
// Normal memory, non-shareable
#define ARM_MPU_SH_NON   (0U)
// Normal memory, outer shareable
#define ARM_MPU_SH_OUTER (2U)
// Normal memory, inner shareable
#define ARM_MPU_SH_INNER (3U)

// Access permissions for EL1-controlled MPU (PRBAR [2:1] AP )
//   RO 1: Read-Only       0: Read-Write
//   NP 1: Non-Privileged  0: Privilegd
#define ARM_MPU_AP_PRIVILEGED               (0U)
#define ARM_MPU_AP_NON_PRIVILEGED           (1U)
#define ARM_MPU_AP_READ_WRITE               (0U)
#define ARM_MPU_AP_READ_ONLY                (1U)
#define ARM_MPU_AP(RO, NP)                  ((((RO & 1U) << 1U) | (NP & 1U)) << 1UL)

// EL1 MPU Access permissions PRBAR[2:1]
// AP = Access permission, RO = Read-only, RW = Read/Write
//
// Normal memory, EL1 read/write, EL0 None.
#define ARM_MPU_AP_EL1_RW_EL0_NONE     (0U)
// Normal memory, EL1 read/write, EL0 read/write.
#define ARM_MPU_AP_EL1_RW_EL0_RW       (1U)
// Normal memory, EL1 read-only, EL0 None.
#define ARM_MPU_AP_EL1_RO_EL0_NONE     (2U)
// Normal memory, EL1 read-only, EL0 read-only.
#define ARM_MPU_AP_EL1_RO_EL0_RO       (3U)

// EL2 MPU Access permissions HPRBAR[2:1]
// AP = Access permission, RO = Read-only, RW = Read/Write
//
// Normal memory, EL1 read/write, EL0 None.
#define ARM_MPU_AP_EL2_RW_EL10_NONE    (0U)
// Normal memory, EL1 read/write, EL0 read/write.
#define ARM_MPU_AP_EL2_RW_EL10_RW      (1U)
// Normal memory, EL1 read-only, EL0 None.
#define ARM_MPU_AP_EL2_RO_EL10_NONE    (2U)
// Normal memory, EL1 read-only, EL0 read-only.
#define ARM_MPU_AP_EL2_RO_EL10_RO      (3U)

// Execute-never PRBAR[0]
// XN = Execute-never, EX = Executable
// Normal memory, Execute-never
#define ARM_MPU_XN                     (1U)
// Normal memory, Executable
#define ARM_MPU_EX                     (0U)


// PRBAR
//   BASE The base address bits[31:5] of a memory region.
//   ATTR = (SH << 6) | (AP << 1) | (XN)
//     SH   Shareability for this memory region
//     AP   Access permission for this memory region
//     XN   Execute-Never bit for this memory region
#define ARM_MPU_PRBAR(BASE, SH, AP, XN)   \
    (((BASE) & MPU_PRBAR_MASK_BASE) | (((SH) << 3) & MPU_PRBAR_MASK_SH) | \
    (((AP) << 1) & MPU_PRBAR_MASK_AP) | ((XN) & MPU_PRBAR_MASK_XN))

#define ARM_MPU_REGION_DISABLE       (0x00)
#define ARM_MPU_REGION_ENABLE        (0x01)

// PRLAR
//   LIMIT   The limit address bits[31:6] of a memory region.
//   ATTRIDX Index a set of attributes of MAIRx registers.
//   EN      Region enable
#define ARM_MPU_PRLAR(LIMIT, ATTRIDX, EN) \
    (((LIMIT) & MPU_PRLAR_MASK_LIMIT) | (((ATTRIDX) << 1UL) & MPU_PRLAR_MASK_ATTRINDX) | (EN))

typedef enum {
    EL1_MPU,            // EL1 controlled MPU
    EL2_MPU             // EL2 controlled MPU
} mpu_type_t;

typedef struct arm_mem_attrs {
    uint8_t attrs[2][4];
} arm_mem_attrs_t;

typedef struct arm_mpu_region {
    uint8_t  number;
    uint8_t  sh;
    uint8_t  ap;
    uint8_t  xn;
    uint8_t  memattr;
    uint8_t  en;
    uint32_t base;
    uint32_t limit;
} arm_mpu_region_t;

typedef struct arm_mpu_config {
    const arm_mpu_region_t*  regions;
    uint8_t            region_num;
} arm_mpu_config_t;

/// @brief Set all el1 mair attribute.
/// @param mair0_attrs
/// @param mair1_attrs
static inline always_inline_function void
arm_mpu_set_mairs(uint32_t mair0, uint32_t mair1)
{
    CP15_SET(MAIR0, mair0);
    CP15_SET(MAIR1, mair1);
}

/// @brief Set all el2 hmair attribute.
/// @param hmair0_attrs
/// @param hmair1_attrs
static inline always_inline_function void
arm_mpu_set_hmairs(uint32_t hmair0, uint32_t hmair1)
{
    CP15_SET(HMAIR0, hmair0);
    CP15_SET(HMAIR1, hmair1);
}

/// @brief Get EL1 MPU region number.
/// @return EL1 MPU region number.
static inline always_inline_function uint8_t
arm_mpu_get_el1_mpu_region_num(void)
{
    return MPUIR_DREGION(CP15_GET(MPUIR));
}

/// @brief Get EL2 MPU region number.
/// @return EL2 MPU region number.
static inline always_inline_function uint8_t
arm_mpu_get_el2_mpu_region_num(void)
{
    return MPUIR_DREGION(CP15_GET(HMPUIR));
}

// Enable EL1-controlled MPU
static inline always_inline_function void
arm_el1_mpu_enable(void)
{
    uint32_t reg_val = CP15_GET(SCTLR) | (SCTLR_M);
    CP15_SET(SCTLR, reg_val);

    ARM_DSB();
    ARM_ISB();
}

// Disable EL1-controlled MPU
static inline always_inline_function void
arm_el1_mpu_disable(void)
{
    uint32_t reg_val = CP15_GET(SCTLR) & (~(SCTLR_M));
    CP15_SET(SCTLR, reg_val);

    ARM_DSB();
    ARM_ISB();
}

// Enable EL1-controller MPU Background
static inline always_inline_function void
arm_el1_mpu_enable_background(uint8_t enable)
{
    uint32_t reg_val = CP15_GET(SCTLR);

    if (enable) {
        reg_val |= SCTLR_BR;
    } else {
        reg_val &= (~(SCTLR_BR));
    }
    CP15_SET(SCTLR, reg_val);

    ARM_DSB();
    ARM_ISB();
}

// Enable EL2-controlled MPU.
static inline always_inline_function void
arm_el2_mpu_enable(void)
{
    uint32_t reg_val = CP15_GET(HSCTLR) | (SCTLR_M);
    CP15_SET(HSCTLR, reg_val);

    ARM_DSB();
    ARM_ISB();
}

// Disable EL2-controlled MPU.
static inline always_inline_function void
arm_el2_mpu_disable(void)
{
    uint32_t reg_val = CP15_GET(HSCTLR) & (~(SCTLR_M));
    CP15_SET(HSCTLR, reg_val);

    ARM_DSB();
    ARM_ISB();
}

// @brief Set one mair attribute.
// @param index [0, 7]
// @param attr
void arm_el1_mpu_set_mair(uint8_t index, uint8_t attr);

// @brief Set all mair attributes.
// @param mem_attrs
void arm_el1_mpu_set_mem_attrs(const arm_mem_attrs_t* mem_attrs);

// @brief Configure mpu regions
// @param regions    array of mpu regions
// @param count      count of mpu regions
int arm_el1_mpu_config(const arm_mpu_region_t* regions, uint8_t count);

// @brief Enable mpu region
// @param index    index of region to be enabled
int arm_el1_mpu_enable_region(uint8_t index);

// @brief Disable mpu region
// @param index    index of region to be disabled
int arm_el1_mpu_disable_region(uint8_t index);

// @brief check if mpu config is valid
// @param config    mpu config
int arm_el1_mpu_check_config(const arm_mpu_config_t* config);

// Macro Wrappers
#define REGION_ENTRY(_name, _no, _base, _limit, _sh, _ap, _xn, _attridx, _en)  \
    {                                                                          \
        .number = _no, .sh = _sh, .ap = _ap, .xn = _xn, .memattr = _attridx,   \
        .en = _en, .base = _base, .limit = _limit                              \
    }

#endif // __CHIPS_E3650_COMMON_MPU_H
